﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using CkSoftware.GroupMe.Sdk.Bot;
using CkSoftware.GroupMe.Sdk.Core;
using CkSoftware.GroupMe.Sdk.Core.Dao;
using CkSoftware.GroupMe.Sdk.Core.Exceptions;
using CkSoftware.GroupMe.Sdk.Groups;
using CkSoftware.GroupMe.Sdk.Groups.Dao;
using CkSoftware.GroupMe.Sdk.Helpers.Authorization;
using CkSoftware.GroupMe.Sdk.Helpers.Emoji;
using CkSoftware.GroupMe.Sdk.Images;
using CkSoftware.GroupMe.Sdk.Messaging;
using CkSoftware.GroupMe.Sdk.TestWeb.Models;

namespace CkSoftware.GroupMe.Sdk.TestWeb.Helpers
{
	public static class GroupMeHelper
	{
		public static GroupMeBot CreateBotForSessionConfig(string botName)
		{
			var apiKey = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeBotApiKeyConfigKey);
			var groupId = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeGroupIdConfigKey);

			if (string.IsNullOrEmpty(apiKey) || string.IsNullOrEmpty(groupId))
			{
				throw new NotSupportedException("The Api-Key and the Group ID must be configured before getting a bot.");
			}

			var webApiAccess = new WebApiAccess("https://api.groupme.com/v3", apiKey);
			var botFactory = new GroupMeBotFactory(webApiAccess);

			return botFactory.CreateBot(botName, groupId);
		}

		public async static Task<PictureData> CreateImage(string imageUrl)
		{
			var apiKey = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeBotApiKeyConfigKey);

			if (string.IsNullOrEmpty(apiKey))
			{
				apiKey = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);
			}

			if (string.IsNullOrEmpty(apiKey))
			{
				throw new NotSupportedException("The Api-Key / Access-Token must be configured before creating an image.");
			}

			var webApiAccess = new WebApiAccess("https://image.groupme.com", apiKey);
			var imageService = new GroupMeImages(webApiAccess);

			return await imageService.CreatePictureAsync(imageUrl, "image");
		}

		public async static Task<IEnumerable<GroupMeBot>> GetAllBotsForSessionConfig()
		{
			var apiKey = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeBotApiKeyConfigKey);

			if (string.IsNullOrEmpty(apiKey))
			{
				throw new NotSupportedException("The Api-Key must be configured before getting a bot.");
			}

			var webApiAccess = new WebApiAccess("https://api.groupme.com/v3", apiKey);
			var botFactory = new GroupMeBotFactory(webApiAccess);

			return await botFactory.GetAllBots();
		}

		public async static Task<GroupsListing> GetGroupsForSessionConfig()
		{
			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}
			
			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			return await groupFactory.GetGroups();
		}

		public async static Task<IEnumerable<GroupMeGroup>> GetFormerGroupsForSessionConfig()
		{
			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}

			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			return await groupFactory.GetFormerGroups();
		}

		public async static Task<GroupMeGroup> GetGroupForSessionConfig(string groupId)
		{
			if (string.IsNullOrEmpty(groupId))
			{
				throw new ArgumentException("The provided group id is not valid.", "groupId");
			}

			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}

			var groupMeGroupSessionKey = string.Format(CultureInfo.CurrentCulture,
			                                           SessionConfiguration.GroupMeGroupKeyFormat,
			                                           groupId);

			if (SessionConfiguration.IsConfigValueIsSet<GroupMeGroup>(groupMeGroupSessionKey))
			{
				return SessionConfiguration.GetConfigValue<GroupMeGroup>(groupMeGroupSessionKey);
			}

			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			var group = await groupFactory.GetGroup(groupId);

			SessionConfiguration.SetConfigValue(groupMeGroupSessionKey, group);

			return group;
		}

		public static void ResetGroupForSessionConfig(string groupId)
		{
			var groupMeGroupSessionKey = string.Format(CultureInfo.CurrentCulture,
													   SessionConfiguration.GroupMeGroupKeyFormat,
													   groupId);

			SessionConfiguration.SetConfigValue<GroupMeGroup>(groupMeGroupSessionKey, null);
		}

		public async static Task<GroupMeGroup> CreateGroup(CreateGroupModel model)
		{
			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}

			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			PictureData image = null;

			if (!string.IsNullOrEmpty(model.GroupImageUrl))
			{
				image = await CreateImage(model.GroupImageUrl);
			}

			return await groupFactory.CreateGroup(model.GroupName, model.Description, image, model.OpenForSharing);
		}

		public async static Task<GroupMeGroup> UpdateGroup(string groupId, CreateGroupModel model)
		{
			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}

			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			PictureData image = null;

			if (!string.IsNullOrEmpty(model.GroupImageUrl))
			{
				image = await CreateImage(model.GroupImageUrl);
			}

			return await groupFactory.UpdateGroup(groupId, model.GroupName, model.Description, image, model.OpenForSharing);
		}

		public async static Task DeleteGroup(string groupId)
		{
			var accessToken = SessionConfiguration.GetConfigValue<string>(SessionConfiguration.GroupMeClientAuthKey);

			if (string.IsNullOrEmpty(accessToken))
			{
				throw new NotSupportedException("The access-token must be configured before getting groups.");
			}

			var authHelper = new GroupMeClientAuthHelper();

			var webApiAccess = authHelper.GetWebApiAccessForAccessToken(accessToken);
			var groupFactory = new GroupMeGroupFactory(webApiAccess);

			await groupFactory.DestroyGroup(groupId);
		}

		public async static Task<GroupAddMemberResultsData> AddGroupMember(string groupId, AddMemberModel addMemberData)
		{
			var group = await GetGroupForSessionConfig(groupId);
			
			var result = await group
				.AddMember(addMemberData.Nickname, addMemberData.UserId, addMemberData.PhoneNumber, addMemberData.Email)
				.SubmitAddedMembersAndWaitForResults();

			ResetGroupForSessionConfig(groupId);

			return result;
		}

		public async static Task RemoveMemberFromGroup(string groupId, string memberId)
		{
			var group = await GetGroupForSessionConfig(groupId);
			await group.RemoveMember(memberId);
			ResetGroupForSessionConfig(groupId);
		}

		public async static Task<IEnumerable<GroupMeMessageData>> GetGroupMessagesForSessionConfig(string groupId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
			                                             SessionConfiguration.GroupMessagesStoreKeyFormat,
			                                             groupId);

			GroupMeMessages messages;

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				var group = await GetGroupForSessionConfig(groupId);
				messages = group.Messages;

				await messages.LoadCurrentMessages();

				SessionConfiguration.SetConfigValue(messagesSessionConfigKey, messages);
			}
			else
			{
				messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			}

			return messages.LoadedMessages;
		}

		public static async Task<IEnumerable<GroupMeMessageData>> LoadOlderMessagesForSessionConfig(string groupId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
			                                             SessionConfiguration.GroupMessagesStoreKeyFormat,
			                                             groupId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
					{
						Code = 500,
						Errors = new[] {"Older messages cannot be loaded before the initial load is done."}
					});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			await messages.ScrollBackward();

			return messages.LoadedMessages;
		}

		public async static Task CreateNewMessageInGroupForSessionConfig(string groupId, string newMessageText, string newMessageImageUrl)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.GroupMessagesStoreKeyFormat,
														 groupId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			GroupMeImageAttachment[] attachments = null;

			if (!string.IsNullOrEmpty(newMessageImageUrl))
			{
				var image = await CreateImage(newMessageImageUrl);
				attachments = new[] {new GroupMeImageAttachment {Url = image.Url}};
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);

			await GroupMeEmojiMessageParser.CreateMessageWithEmojiImages(messages, Guid.NewGuid().ToString(), newMessageText, attachments: attachments);
			
			// reset messages and load the most recent messages.
			await messages.LoadCurrentMessages();
		}

		public async static Task LikeMessageInGroupForSessionConfig(string groupId, string messageId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.GroupMessagesStoreKeyFormat,
														 groupId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			await messages.LikeMessage(messageId);
		}

		public async static Task UnlikeMessageInGroupForSessionConfig(string groupId, string messageId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.GroupMessagesStoreKeyFormat,
														 groupId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			await messages.UnlikeMessage(messageId);
		}

		public async static Task RefreshGroupMessagesForSessionConfig(string groupId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.GroupMessagesStoreKeyFormat,
														 groupId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			
			// reset messages and load the most recent messages.
			await messages.LoadCurrentMessages();
		}

		public async static Task<IEnumerable<GroupMeMessageData>> GetDirectMessagesForSessionConfig(string groupId, string memberId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.DirectMessagesStoreKeyFormat,
														 memberId);

			GroupMeMessages messages;

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				var group = await GetGroupForSessionConfig(groupId);
				var member = group.Data.Members.Single(m => m.UserId == memberId);
				messages = member.Messages.Value;

				await messages.LoadCurrentMessages();

				SessionConfiguration.SetConfigValue(messagesSessionConfigKey, messages);
			}
			else
			{
				messages = SessionConfiguration.GetConfigValue<GroupMeDirectMessages>(messagesSessionConfigKey);
			}

			return messages.LoadedMessages;
		}

		public async static Task<IEnumerable<GroupMeMessageData>> LoadOlderDirectMessagesForSessionConfig(string groupId, string memberId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
											 SessionConfiguration.DirectMessagesStoreKeyFormat,
											 memberId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeDirectMessages>(messagesSessionConfigKey);
			await messages.ScrollBackward();

			return messages.LoadedMessages;
		}

		public async static Task CreateNewDirectMessageForSessionConfig(string groupId, string memberId, string newMessageText, string newMessageImageUrl)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
											 SessionConfiguration.DirectMessagesStoreKeyFormat,
											 memberId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			GroupMeImageAttachment[] attachments = null;

			if (!string.IsNullOrEmpty(newMessageImageUrl))
			{
				var image = await CreateImage(newMessageImageUrl);
				attachments = new[] { new GroupMeImageAttachment { Url = image.Url } };
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeDirectMessages>(messagesSessionConfigKey);

			await GroupMeEmojiMessageParser.CreateMessageWithEmojiImages(messages, Guid.NewGuid().ToString(), newMessageText, attachments: attachments);
			
			// reset messages and load the most recent messages.
			await messages.LoadCurrentMessages();
		}

		public async static Task LikeDirectMessageForSessionConfig(string groupId, string memberId, string messageId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
														 SessionConfiguration.DirectMessagesStoreKeyFormat,
														 memberId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			await messages.LikeMessage(messageId);
		}

		public async static Task UnlikeDirectMessageForSessionConfig(string groupId, string memberId, string messageId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
											 SessionConfiguration.DirectMessagesStoreKeyFormat,
											 memberId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeMessages>(messagesSessionConfigKey);
			await messages.UnlikeMessage(messageId);
		}

		public async static Task RefreshDirectMessagesForSessionConfig(string groupId, string memberId)
		{
			var messagesSessionConfigKey = string.Format(CultureInfo.CurrentCulture,
											 SessionConfiguration.DirectMessagesStoreKeyFormat,
											 memberId);

			if (!SessionConfiguration.IsConfigValueIsSet<GroupMeDirectMessages>(messagesSessionConfigKey))
			{
				throw new GroupMeException(new MetaData
				{
					Code = 500,
					Errors = new[] { "Older messages cannot be loaded before the initial load is done." }
				});
			}

			var messages = SessionConfiguration.GetConfigValue<GroupMeDirectMessages>(messagesSessionConfigKey);

			// reset messages and load the most recent messages.
			await messages.LoadCurrentMessages();
		}
	}
}